// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

/**
 * @file
 *
 * This file declares the ABI converter APIs for codegen.
 */

#ifndef CANGJIE_CODEGEN_CGCFFI_H
#define CANGJIE_CODEGEN_CGCFFI_H

#include <unordered_map>

#include "IRBuilder.h"
#include "cangjie/CHIR/Type/Type.h"

namespace Cangjie {
namespace CodeGen {

/**
 * A class used for generating code related to the CFFI feature.
 */
class CGCFFI {
public:
    using LLVMFuncArgIt = llvm::Function::arg_iterator;

    virtual ~CGCFFI() = default;

    /**
     * Get CodeGen function type by CHIR::FuncType. It's platform-related.
     * If any incomplete struct type exists in @p funcTy, this method will return a nullptr
     * and caller should check this and get a literal struct pointer type ({}*) directly if yes.
     *
     * @param funcTy It must be CFunc.
     */
    virtual llvm::FunctionType* GetCFuncType(const CHIR::FuncType& chirFuncTy) = 0;
    /**
     * Add necessary attributes for function. It's platform-related.
     *
     * @param funcTy It must be CFunc.
     */
    virtual void AddFunctionAttr(const CHIR::FuncType& chirFuncTy, llvm::Function& func) = 0;
    /**
     * Determine whether to perform platform-related processing on the i-th parameter.
     *
     * @param funcTy It must be CFunc.
     */
    virtual bool NeedProcessParam(const CHIR::FuncType& chirFuncTy, size_t index) = 0;
    /**
     * Handles a specific parameter of a function.
     * This method needs to be used together with `NeedProcessParam`.
     * This function is invoked when the return result of `NeedProcessParam` is true.
     *
     * @param paramTy Parameter types in AST.
     * @param arg The specific parameter.
     * @param place The value used to hold the value in @p arg.
     * @param builder An IRBuilder to generate the necessary code.
     */
    virtual void ProcessParam(
        CHIR::Type& chirParamTy, LLVMFuncArgIt& arg, llvm::Value* place, IRBuilder2& builder) = 0;
    /**
     * Process parameters at CFunc calls.
     *
     * @param funcTy It must be CFunc.
     * @param args Existing parameter for function call, which may be modified.
     * @param builder An IRBuilder to generate the necessary code.
     *
     * @return True if the return value of this function call needs to be processed additionally. Otherwise false.
     */
    virtual bool ProcessInvocation(
        const CHIR::FuncType& chirFuncTy, std::vector<CGValue*>& args, IRBuilder2& builder) = 0;

    /**
     * Process result of call inst.
     * This function can to be called only when the result of ProcessInvocation is true.
     *
     * @param retTy Callee's return type.
     * @param ret The call instruction.
     * @param sret The value used to hold the value in @p ret, it can be generated by CreateEntryAlloca.
     * @param builder An IRBuilder to generate the necessary code.
     *
     * @return Always @p sret now.
     */
    virtual llvm::Value* ProcessCallRet(
        const CHIR::Type& chirRetTy, llvm::Value& ret, llvm::Value& sret, IRBuilder2& builder)
    {
        auto retType = ret.getType();
        auto place = builder.CreateEntryAlloca(retType, nullptr, "cffi");
        builder.CreateStore(&ret, place);

        auto& cgMod = builder.GetCGModule();
        size_t actualSize = GetTypeSize(cgMod, chirRetTy);
        builder.CreateMemCpy(&sret, GetAlign(cgMod, chirRetTy), place, GetAlign(cgMod, *retType), actualSize);

        return &sret;
    }
    /**
     * Process the return value of CFunc on demand.
     *
     * @param retType Return type of the function.
     * @param val Return type of the function, its type can different with @p retType.
     * @param builder An IRBuilder to generate the necessary code.
     *
     * @return The final return value of the function, it can be same with @p val.
     */
    virtual llvm::Value* ProcessRetValue(const llvm::Type& retType, llvm::Value& val, IRBuilder2& builder) = 0;

protected:
    llvm::MaybeAlign GetAlign(const CGModule& cgMod, const CHIR::Type& chirTy) const
    {
        return llvm::MaybeAlign(GetTypeAlignment(cgMod, chirTy));
    }
    llvm::MaybeAlign GetAlign(const CGModule& cgMod, llvm::Type& llvmTy) const
    {
        return llvm::MaybeAlign(GetTypeAlignment(cgMod, llvmTy));
    }
};
} // namespace CodeGen
} // namespace Cangjie
#endif
